`include "MacroDef.v"

//THE ADDR[2:0] IS SELECT BITS
`define Index_index         8'b00000_000
`define Random_index        8'b00001_000
`define EntryLo0_index      8'b00010_000
`define EntryLo1_index      8'b00011_000
`define Context_index       8'b00100_000
`define PageMask_index      8'b00101_000
`define Wired_index         8'b00110_000
`define BadVAddr_index      8'b01000_000
`define Count_index         8'b01001_000
`define EntryHi_index       8'b01010_000
`define Compare_index       8'b01011_000
`define Status_index        8'b01100_000
`define Cause_index         8'b01101_000
`define EPC_index           8'b01110_000
`define PRId_index          8'b01111_000
`define Ebase_index         8'b01111_001
`define Config_index        8'b10000_000
`define Config1_index       8'b10000_001
`define TagLo_index         8'b11100_010
`define TagHi_index         8'b11101_010


`define status_bev          Status[22]
`define status_im           Status[15:8]
`define status_exl          Status[1]      //例外级
`define status_ie           Status[0]      //全局中断使能位
`define cause_bd            Cause[31]      //是否处于分支延迟槽
`define cause_ti            Cause[30]      //计时器中断提示
`define cause_ip1           Cause[15:10]    //待处理硬件中断，每一位对应一个中断线
`define cause_ip2           Cause[9:8]      //待处理软件中断标识
`define cause_excode        Cause[6:2]     //例外编码


module CP0(
    clk, rst, CP0WrEn, addr, data_in, MEM_Exc, MEM_eret_flush, 
    MEM_bd,ext_int_in, MEM_ExcCode, MEM_badvaddr, MEM_PC, dcache_stall,

    data_out, EPC_out, Interrupt
);
    input clk;
    input rst;
    input CP0WrEn;
    input [7:0] addr;
    input [31:0] data_in;
    input MEM_Exc;           //M级中断标识
    input MEM_eret_flush;           //M级eret指令清空信号
    input MEM_bd;            //延迟槽标识
    input [5:0] ext_int_in;       //外部硬件中断标识
    input [4:0] MEM_ExcCode;        //M级例外编码
    input [31:0]  MEM_badvaddr;      //bad virtual address
    input [31:0] MEM_PC;  
    input dcache_stall;

    output reg[31:0] data_out;
    output [31:0] EPC_out;
    output Interrupt;           //中断

    //CP0 Reg, it could be read and generated by mfc0, mtc0 and tlb instr
    //reg [31:0] Index;         
    //reg [31:0] Random;        
    //reg [31:0] EntryLo0;     
    //reg [31:0] EntryLo1;      
    //reg [31:0] Context;       
    //reg [31:0] PageMask;      
    //reg [31:0] Wired;         
    reg [31:0] BadVAddr;      
    reg [31:0] Count;         
    //reg [31:0] EntryHi;       
    reg [31:0] Compare;      
    reg [31:0] Status;        
    reg [31:0] Cause;         
    reg [31:0] EPC;           
    //reg [31:0] PRId;          
    //reg [31:0] Ebase;        
    //reg [31:0] Config;        
    //reg [31:0] Config1;       
    //reg [31:0] TagLo;         
    //reg [31:0] TagHi;    
         
    wire count_eq_compare;       //Count == Compare
    reg tick;                   

    assign count_eq_compare = (Compare == Count);
    assign Interrupt =  |(Cause[15:8] & `status_im)  & (`status_ie & ~`status_exl);
    assign EPC_out = EPC;
    //used for TLB


    //MFC0 read CP0
    always @(*) begin
        case (addr)
            //`Index_index: data_out = Index;
            //`Random_index: data_out = Random;
            //`EntryLo0_index: data_out = EntryLo0;
            //`EntryLo1_index: data_out = EntryLo1;
            //`Context_index: data_out = Context;
            //`PageMask_index: data_out = PageMask;
            //`Wired_index: data_out = Wired;
            `BadVAddr_index: data_out = BadVAddr;
            `Count_index: data_out = Count;
            //`EntryHi_index: data_out = EntryHi;
            `Compare_index: data_out = Compare;
            `Status_index: data_out = Status;
            `Cause_index: data_out = Cause;
            `EPC_index: data_out = EPC;
            //`PRId_index: data_out = PRId;
            //`Ebase_index: data_out = Ebase;
            //`Config_index: data_out = Config;
            //`Config1_index: data_out = Config1;
            //`TagLo_index: data_out = TagLo;
            //`TagHi_index: data_out = TagHi;
            default: data_out = 32'd0;
        endcase  
    end

            
    /*CP0 generation by mtc0 and tlb instr*/
    //Index generation
    /*
    always @(posedge clk) begin
        if (CP0WrEn && addr == `Index_index)
            Index <= data_in;
    end

    //EntryHi generation,consider the priority 
    always @(posedge clk) begin
        if (CP0WrEn && addr == `EntryHi_index)
            EntryHi <= data_in;
    end

    //EntryLo0 generation
    always @(posedge clk) begin
        if (CP0WrEn && addr == `EntryLo0_index)
            EntryLo0 <= data_in;
    end

    //EntryLo1 generation
    always @(posedge clk) begin
        if (CP0WrEn && addr == `EntryLo1_index)
            EntryLo1 <= data_in;
    end
    */

    //BadVAddr generation
    always @(posedge clk) begin
        if (CP0WrEn && addr == `BadVAddr_index)
            BadVAddr <= data_in;
        else if ( (MEM_Exc && (MEM_ExcCode == `AdEL || MEM_ExcCode == `AdES)))
            BadVAddr <= MEM_badvaddr;
    end

    //Count generation
    always @(posedge clk) begin
        if (!rst)
            tick <= 1'b0;
        else 
            tick <= ~tick;
        if (CP0WrEn && addr == `Count_index)
            Count <= data_in;
        else if (tick)
            Count <= Count + 1'b1;
    end

    //Compare generation
    always @(posedge clk) begin
        if (CP0WrEn && addr == `Compare_index)
            Compare <= data_in;
    end

    //Status
    always @(posedge clk) begin
        if (!rst)
            `status_bev <= 1'b1;
    end

    //Status
    always @(posedge clk) begin
        if ((CP0WrEn && addr == `Status_index) & ~dcache_stall)
            `status_im <= data_in[15:8];
    end

    //Status
    always @(posedge clk) begin
        if (!rst)
            `status_exl <= 1'b0;
        else if (MEM_Exc)
            `status_exl <= 1'b1;
        else if (MEM_eret_flush)
            `status_exl <= 1'b0;
        else if ((CP0WrEn && addr == `Status_index & ~dcache_stall))
            `status_exl <= data_in[1];
    end

    //Status
    always @(posedge clk) begin
        if (!rst)
            `status_ie <= 1'b0;
        else if ((CP0WrEn && addr == `Status_index) & ~dcache_stall)
            `status_ie <= data_in[0];
    end

    //Status
    always @(posedge clk) begin
        if (!rst) begin
            Status[31:23] <= 9'b0;
            Status[21:16] <= 6'b0;
            Status[7:2] <= 6'b0;
        end
    end

    //Cause
    always @(posedge clk) begin
        if (!rst)
            `cause_bd <= 1'b0;
        else if (MEM_Exc && !`status_exl)
            `cause_bd <= MEM_bd;
    end

    //Cause
    always @(posedge clk) begin
        if (!rst)
            `cause_ti <= 1'b0;
        else if (CP0WrEn && addr == `Compare_index)
            `cause_ti <= 1'b0;
        else if (count_eq_compare)
            `cause_ti <= 1'b1;
    end

    //Cause
    always @(posedge clk) begin
        if (!rst)
            `cause_ip1 <= 6'b0;
        else begin
            Cause[15] <= ext_int_in[5] | `cause_ti;
            Cause[14:10] <= ext_int_in[4:0];
        end
    end

    //Cause
    always @(posedge clk) begin
        if (!rst)
            `cause_ip2 <= 2'b0;
        else if ((CP0WrEn && addr == `Cause_index) & ~dcache_stall)
            `cause_ip2 <= data_in[9:8];
    end

    //Cause
    always @(posedge clk) begin
        if (!rst)
            `cause_excode <= 5'b0;
        else if (MEM_Exc)
            `cause_excode <= MEM_ExcCode;
    end

    //Cause
    always @(posedge clk) begin
        if (!rst) begin
            Cause[29:16] <= 14'b0;
            Cause[7] <= 1'b0;
            Cause[1:0] <= 2'b0;
        end
    end

    //EPC
    always @(posedge clk) begin
        if (MEM_Exc && !`status_exl) begin
            EPC <= MEM_bd ? MEM_PC - 4 : MEM_PC;
        end   
        else if (CP0WrEn && addr == `EPC_index) begin
            EPC <= data_in;
         end  
    end
    
endmodule